package org.jivesoftware.smack.sasl;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;

import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
import de.measite.smack.Sasl;

import org.jivesoftware.smack.SASLAuthentication;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.util.Base64;

/**
 * This class is originally from
 * http://code.google.com/p/fbgc/source/browse/trunk
 * /daemon/src/main/java/org/albino/mechanisms/FacebookConnectSASLMechanism.java
 * I just adapted to match the SMACK package scheme and
 */
public class SASLFacebookConnect extends SASLMechanism {

	private String sessionKey = "";
	private String sessionSecret = "";
	private String apiKey = "";

	static {
		SASLAuthentication.registerSASLMechanism("X-FACEBOOK-PLATFORM",
				SASLFacebookConnect.class);
		SASLAuthentication.supportSASLMechanism("X-FACEBOOK-PLATFORM", 0);
	}

	public SASLFacebookConnect(SASLAuthentication saslAuthentication) {
		super(saslAuthentication);
	}

	// protected void authenticate() throws IOException, XMPPException {
	// String[] mechanisms = { getName() };
	// Map<String, String> props = new HashMap<String, String>();
	// sc = Sasl.createSaslClient(mechanisms, null, "xmpp", hostname, props,
	// this);
	//
	// super.authenticate();
	// }

	protected void authenticate() throws IOException, XMPPException {
		final StringBuilder stanza = new StringBuilder();
		stanza.append("<auth mechanism=\"").append(getName());
		stanza.append("\" xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
		stanza.append("</auth>");

		// Send the authentication to the server
		getSASLAuthentication().send(new Packet() {

			@Override
			public String toXML() {
				return stanza.toString();
			}

		});
	}

	public void authenticate(String apiKeyAndSessionKey, String host,
			String sessionSecret) throws IOException, XMPPException {

		if (apiKeyAndSessionKey == null || sessionSecret == null)
			throw new IllegalStateException("Invalid parameters!");

		String[] keyArray = apiKeyAndSessionKey.split("\\|");

		if (keyArray == null || keyArray.length != 2)
			throw new IllegalStateException(
					"Api key or session key is not present!");

		this.apiKey = keyArray[0];
		this.sessionKey = keyArray[1];
		this.sessionSecret = sessionSecret;

		this.authenticationId = sessionKey;
		this.password = sessionSecret;
		this.hostname = host;

		String[] mechanisms = { "DIGEST-MD5" };
		Map<String, String> props = new HashMap<String, String>();
		sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, this);
		authenticate();
	}

	public void authenticate(String username, String host, CallbackHandler cbh)
			throws IOException, XMPPException {
		String[] mechanisms = { "DIGEST-MD5" };
		Map<String, String> props = new HashMap<String, String>();
		sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, cbh);
		authenticate();
	}

	protected String getName() {
		return "X-FACEBOOK-PLATFORM";
	}

	public void challengeReceived(String challenge) throws IOException {
		// Build the challenge response stanza encoding the response text
		final StringBuilder stanza = new StringBuilder();

		byte response[] = null;
		if (challenge != null) {
			String decodedResponse = new String(Base64.decode(challenge));
			Map<String, String> parameters = getQueryMap(decodedResponse);

			String version = "1.0";
			String nonce = parameters.get("nonce");
			String method = parameters.get("method");

			Long callId = new GregorianCalendar().getTimeInMillis() / 1000;

			String sig = "api_key=" + apiKey + "call_id=" + callId + "method="
					+ method + "nonce=" + nonce + "session_key=" + sessionKey
					+ "v=" + version + sessionSecret;

			try {
				sig = MD5(sig);
			} catch (NoSuchAlgorithmException e) {
				throw new IllegalStateException(e);
			}

			String composedResponse = "api_key=" + apiKey + "&" + "call_id="
					+ callId + "&" + "method=" + method + "&" + "nonce="
					+ nonce + "&" + "session_key=" + sessionKey + "&" + "v="
					+ version + "&" + "sig=" + sig;

			response = composedResponse.getBytes();
		}

		String authenticationText = "";

		if (response != null) {
			authenticationText = Base64.encodeBytes(response,
					Base64.DONT_BREAK_LINES);
		}

		stanza.append("<response xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
		stanza.append(authenticationText);
		stanza.append("</response>");

		// Send the authentication to the server
		getSASLAuthentication().send(new Packet() {

			@Override
			public String toXML() {
				return stanza.toString();
			}

		});
	}

	private Map<String, String> getQueryMap(String query) {
		String[] params = query.split("&");
		Map<String, String> map = new HashMap<String, String>();
		for (String param : params) {
			String name = param.split("=")[0];
			String value = param.split("=")[1];
			map.put(name, value);
		}
		return map;
	}

	private String convertToHex(byte[] data) {
		StringBuffer buf = new StringBuffer();
		for (int i = 0; i < data.length; i++) {
			int halfbyte = (data[i] >>> 4) & 0x0F;
			int two_halfs = 0;
			do {
				if ((0 <= halfbyte) && (halfbyte <= 9))
					buf.append((char) ('0' + halfbyte));
				else
					buf.append((char) ('a' + (halfbyte - 10)));
				halfbyte = data[i] & 0x0F;
			} while (two_halfs++ < 1);
		}
		return buf.toString();
	}

	public String MD5(String text) throws NoSuchAlgorithmException,
			UnsupportedEncodingException {
		MessageDigest md;
		md = MessageDigest.getInstance("MD5");
		byte[] md5hash = new byte[32];
		md.update(text.getBytes("iso-8859-1"), 0, text.length());
		md5hash = md.digest();
		return convertToHex(md5hash);
	}
}
